#!/usr/bin/env python3

import rospy
from std_msgs.msg import String
from playsound import playsound
import os
import time


class VoicePlayer:
    def __init__(self, audio_base_path: str):
        # Normalize to absolute path for robustness when launched from ROS
        if not os.path.isabs(audio_base_path):
            audio_base_path = os.path.abspath(audio_base_path)

        if not os.path.exists(audio_base_path):
            try:
                os.makedirs(audio_base_path, exist_ok=True)
                rospy.loginfo(f"Audio directory '{audio_base_path}' created.")
            except OSError as exc:
                rospy.logerr(f"Error creating audio directory '{audio_base_path}': {exc}")
                raise ValueError(f"Failed to create specified audio path: {audio_base_path}")

        self.audio_dir = audio_base_path

        # Domain constraints
        self.allowed_colors = ["red", "orange", "blue", "yellow", "green"]
        # Dashboard indices are digits mapped from english words
        self.allowed_dashboards = ["1", "2", "3", "4", "5", "6"]
        self.allowed_states = ["normal", "high", "low"]  # 'up' -> 'high', 'down' -> 'low'

        # Mappings
        self.number_word_to_digit = {
            "one": "1",
            "two": "2",
            "three": "3",
            "four": "4",
            "five": "5",
            "six": "6",
        }
        self.state_word_to_state = {
            "up": "high",
            "normal": "normal",
            "down": "low",
        }

        rospy.loginfo(
            f"VoicePlayer ready. audio_dir={self.audio_dir}, colors={self.allowed_colors}, dashboards={self.allowed_dashboards}, states={self.allowed_states}"
        )

    def play_triplet(self, color: str, dashboard_digit: str, state: str) -> None:
        try:
            if color not in self.allowed_colors:
                raise ValueError(f"Invalid color: {color}. Allowed: {self.allowed_colors}")
            if dashboard_digit not in self.allowed_dashboards:
                raise ValueError(f"Invalid dashboard: {dashboard_digit}. Allowed: {self.allowed_dashboards}")
            if state not in self.allowed_states:
                raise ValueError(f"Invalid state: {state}. Allowed: {self.allowed_states}")

            color_audio = f"{color}_area.mp3"
            dashboard_audio = f"dashboard_{dashboard_digit}.mp3"
            state_audio = f"{state}.mp3"
            sequence = [color_audio, dashboard_audio, state_audio]

            for filename in sequence:
                full_path = os.path.join(self.audio_dir, filename)
                if os.path.exists(full_path):
                    rospy.loginfo(f"Playing: {full_path}")
                    playsound(full_path)
                    time.sleep(0.1)
                else:
                    rospy.logwarn(f"Audio file missing: {full_path} (skipping)")
        except Exception as exc:
            rospy.logerr(f"Playback failed: {exc}")

    def parse_and_play(self, message: str) -> None:
        # Expected: "color-number-word-state" e.g. "red-one-up"
        parts = [p.strip().lower() for p in message.split("-")]
        if len(parts) != 3:
            rospy.logwarn(
                f"Invalid message '{message}'. Expected 'color-number-state', e.g. 'red-one-up'"
            )
            return

        color_word, number_word, state_word = parts

        # Map english number to digit
        digit = self.number_word_to_digit.get(number_word)
        if digit is None:
            rospy.logwarn(
                f"Unsupported number word '{number_word}'. Allowed: {list(self.number_word_to_digit.keys())}"
            )
            return

        # Map state synonyms
        mapped_state = self.state_word_to_state.get(state_word)
        if mapped_state is None:
            rospy.logwarn(
                f"Unsupported state '{state_word}'. Allowed: {list(self.state_word_to_state.keys())}"
            )
            return

        self.play_triplet(color_word, digit, mapped_state)


class YoloResultVoiceNode:
    def __init__(self) -> None:
        rospy.init_node("yolo_result_voice_player", anonymous=True)

        # Parameter: audio directory; default to 'voice/audio/' relative to repo root if not provided
        audio_path = rospy.get_param("~audio_path", None)
        if not audio_path:
            base_dir = os.path.dirname(__file__)
            repo_root = os.path.abspath(os.path.join(base_dir, "..", "..", "..", ".."))
            audio_path = os.path.join(repo_root, "voice", "audio")

        try:
            self.player = VoicePlayer(audio_base_path=audio_path)
        except ValueError as exc:
            rospy.logerr(f"Initialization failed: {exc}")
            rospy.signal_shutdown(str(exc))
            return

        self.sub = rospy.Subscriber("/yolo_result", String, self._on_result)
        rospy.loginfo("Subscribed to '/yolo_result' (format: 'color-number-state', e.g. 'yellow-two-normal')")

    def _on_result(self, msg: String) -> None:
        rospy.loginfo(f"/yolo_result: '{msg.data}'")
        self.player.parse_and_play(msg.data)

    def run(self) -> None:
        rospy.spin()


if __name__ == "__main__":
    try:
        node = YoloResultVoiceNode()
        node.run()
    except rospy.ROSInterruptException:
        pass
    except Exception as exc:
        rospy.logerr(f"Node failure: {exc}")


